//=====================================================================
//
//  File:      ascmd.cs
//  Summary:   A command-line utility to execute Analysis Services XMLA scripts
//             or MDX queries
//  Authors:   Dave Wickert (dwickert@microsoft.com)
//  Date:	   03-Jan-2006
//
//---------------------------------------------------------------------
//
//  This file is part of the Microsoft SQL Server Code Samples.
//  Copyright (C) Microsoft Corporation.  All rights reserved.
//
//This source code is intended only as a supplement to Microsoft
//Development Tools and/or on-line documentation.  See these other
//materials for detailed information regarding Microsoft code samples.
//
//THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF 
//ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO 
//THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
//PARTICULAR PURPOSE.
//
//===================================================================== 

// Note: the code below contains additional lines to check for strong
// naming convention of obects, e.g. server names, usernames, passwords, etc.
// However, since we don't know what you guidelines will be we were not
// able to assume your specific values. Free from to change ParseArgs and add
// minimum, maximum and invalid object characters based on your 
// individual naming conventions.

// Line length (limit to 79 characters for formatting)
//       1         2         3         4         5         6         7
//34567890123456789012345678901234567890123456789012345678901234567890123456789

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Security.Permissions;
using System.Security.Principal;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Web;
using System.Xml;
using Deployment.CustomTasks.Properties;
using Microsoft.AnalysisServices;
using Assembly=System.Reflection.Assembly;
//AMO
	//ADOMD.NET
// needed for Logon Win32 API call and user impersonation //

[assembly : CLSCompliant(true)]

namespace CustomNAntTasks.ASCmd
{
	internal static class NativeMethods
	{
		[DllImport("advapi32.dll", SetLastError = true)]
		[return : MarshalAs(UnmanagedType.Bool)]
		public static extern bool LogonUser(String lpszUsername, String lpszDomain, String lpszPassw_outord,
		                                    int dwLogonType, int dwLogonProvider, ref IntPtr phToken);
	}

	public class ASCmdImpl
	{
		#region Assembly Attribute Accessors

		//static string AssemblyTitle // not used yet
		//{
		//    get
		//    {
		//        // Get all Title attributes on this assembly
		//        object[] attributes = System.Reflection.Assembly.GetExecutingAssembly().GetCustomAttributes(typeof(AssemblyTitleAttribute), false);
		//        // If there is at least one Title attribute
		//        if (attributes.Length > 0)
		//        {
		//            // Select the first one
		//            AssemblyTitleAttribute titleAttribute = (AssemblyTitleAttribute)attributes[0];
		//            // If it is not an empty string, return it
		//            if (titleAttribute.Title != "")
		//                return titleAttribute.Title;
		//        }
		//        // If there was no Title attribute, or if the Title attribute was the empty string, return the .exe name
		//        return System.IO.Path.GetFileNameWithoutExtension(System.Reflection.Assembly.GetExecutingAssembly().CodeBase);
		//    }
		//}

		private static string AssemblyVersion
		{
			get { return Assembly.GetExecutingAssembly().GetName().Version.ToString(); }
		}

		private static string AssemblyDescription
		{
			get
			{
				// Get all Description attributes on this assembly
				object[] attributes =
					Assembly.GetExecutingAssembly().GetCustomAttributes(typeof (AssemblyDescriptionAttribute), false);
				// If there aren't any Description attributes, return an empty string
				if (attributes.Length == 0)
					return "";
				// If there is a Description attribute, return its value
				return ((AssemblyDescriptionAttribute) attributes[0]).Description;
			}
		}

		private static string AssemblyProduct // not used yet
		{
			get
			{
				// Get all Product attributes on this assembly
				object[] attributes = Assembly.GetExecutingAssembly().GetCustomAttributes(typeof (AssemblyProductAttribute), false);
				// If there aren't any Product attributes, return an empty string
				if (attributes.Length == 0)
					return "";
				// If there is a Product attribute, return its value
				return ((AssemblyProductAttribute) attributes[0]).Product;
			}
		}

		private static string AssemblyCopyright
		{
			get
			{
				// Get all Copyright attributes on this assembly
				object[] attributes =
					Assembly.GetExecutingAssembly().GetCustomAttributes(typeof (AssemblyCopyrightAttribute), false);
				// If there aren't any Copyright attributes, return an empty string
				if (attributes.Length == 0)
					return "";
				// If there is a Copyright attribute, return its value
				return ((AssemblyCopyrightAttribute) attributes[0]).Copyright;
			}
		}

		//static string AssemblyCompany // not used yet
		//{
		//    get
		//    {
		//        // Get all Company attributes on this assembly
		//        object[] attributes = System.Reflection.Assembly.GetExecutingAssembly().GetCustomAttributes(typeof(AssemblyCompanyAttribute), false);
		//        // If there aren't any Company attributes, return an empty string
		//        if (attributes.Length == 0)
		//            return "";
		//        // If there is a Company attribute, return its value
		//        return ((AssemblyCompanyAttribute)attributes[0]).Company;
		//    }
		//}

		private static string AssemblyProcessorArchitecture
		{
			get { return Assembly.GetExecutingAssembly().GetName().ProcessorArchitecture.ToString(); }
		}

		#endregion

		#region Class Variables

		// message handling for loc strings
		private static string Msg(string locString)
		{
			return (AssemblyProduct + ": " + locString);
		}

		// What options have been seen on the command line
		private static bool Option_U_specified;
		private static bool Option_P_specified;
		private static bool Option_S_specified;
		private static bool Option_d_specified;
		private static bool Option_t_specified;
		private static bool Option_tc_specified;
		private static bool Option_i_specified;
		private static bool Option_o_specified;
		private static bool Option_T_specified;
		private static bool Option_Tt_specifed;
		private static bool Option_Tf_specified;
		private static bool Option_Tl_specified;
		private static bool Option_Td_specified;
		private static bool Option_Q_specified;
		private static bool Option_xc_specified;
		private static bool Option_v_specified;

		// Variables for options
		private static string UserName = "";
		private static string Domain = "";
		private static string Password = "";
		private static string Server = "localhost";
		private static string Instance = "";
		private static string Database = "";
		private static string InputFile = "";
		private static string OutputFile = "";
		private static string TraceFile = "";
		private static string Query = "";
		private static string ExtendedConnectstring = "";
		private static string TraceFormat = "csv"; // use the -f option
		private static string Timeout = "";
		private static string ConnectTimeout = "";
		private static string TraceTimeout = "5"; // trace is finished if no activity for 5 seconds
		private static bool httpConnection;

		// provider to use -- should we have this an an option (i.e. -p) ??
		[SuppressMessage("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")] private const string Provider =
			"MSOLAP.3"; // force AS2K5 provider

		// Variables for read/writing
		private static StringBuilder sb = new StringBuilder(); // input stream
		private static StreamWriter sw_out; //Output file
		private static bool IsOutputFileOpen;
		private static StreamWriter sw_trace; //Trace file
		private static bool IsTraceFileOpen;
		private static string TraceDelim = "|";
		private static string TraceLevel = "high"; // default is to trace at the high level
		private static long BeginEndBlockCount; // if 0 then OK to immediately finish trace
		private static int TraceTimeoutCount; // if goes to 0, then finish trace regardless of BeginEndBlockCount
		private static int TraceTimeoutCountReset; // the amount of time to wait once trace events stop
		private static bool TraceStarted; // have the trace events started to flow?

		private const int PollingInterval = 20;
		                  // Every 1/20th of a second (50ms) see if trace is done (BeginEndBlockCount drives to 0)

		private static bool ExceptionSeen; // have we seen an exception in the xmla return stream

		private static bool _DEBUGMODE; // Set to indicate that we need debug messages outputted

		// substitution table -- includes -v entries plus command-line arguments
		private static Dictionary<string, string> substituteWith =
			new Dictionary<string, string>(StringComparer.CurrentCultureIgnoreCase);

		// maximum timeout values (if used)
		private const int maxTimeout = -1; // -1 means don't check

		// regular expressions used
		private const string ScriptingVarRefRegex = @"\$\([a-zA-Z0-9_-]+\)";
		private const string ScriptingVarNameRegex = @"^[a-zA-Z0-9_-]+=.*";

		private const string XMLACommandRegex =
			@"(?sx)^(<Alter.*|
                                 <Backup.*|
                                 <Batch.*|
                                 <BeginTransaction.*|
                                 <Cancel.*|
                                 <ClearCache.*|
                                 <CommitTransaction.*|
                                 <Create.*|
                                 <Delete.*|
                                 <DesignAggregations.*|
                                 <Drop.*|
                                 <Insert.*|
                                 <Lock.*|
                                 <MergePartitions.*|
                                 <NotifyTableChange.*|
                                 <Process.*|
                                 <Restore.*|
                                 <RollbackTransaction.*|
                                 <SetPasswordEncryptionKey.*|
                                 <Statement.*|
                                 <Subscribe.*|
                                 <Synchronize.*|
                                 <Unlock.*|
                                 <Update.*|
                                 <UpdateCells.*)$";

		private const string DiscoverRegex = @"(?sx)^<Discover.*$";
		private const string ExecuteRegex = @"(?sx)^<Execute.*$";
		private const string BatchRegex = @"^[\w\W]*?[\r\n]go\s";

		// formats
		private const string SoapEnvFormat =
			@"<Envelope xmlns='http://schemas.xmlsoap.org/soap/envelope/'>
                <Body>
                {0}
                </Body>
              </Envelope>";

		#endregion

		// --------------------------------------------------------------------
		// Main routine -- called with the command line arguments
		public static int Main(string[] args)
		{
			try
			{
				Console.WriteLine(AssemblyDescription);
				Console.WriteLine(Resources.locVersion,
				                  AssemblyVersion, AssemblyProcessorArchitecture);
				Console.WriteLine(AssemblyCopyright);

				// Parse the command line argument list, verify the options and
				// open any requested files
				try
				{
					if (!ParseArgs(args))
					{
						return 1; // if it returns an error, just exit
					}
				}
				catch (Exception ex)
				{
					Console.WriteLine(Msg(Resources.locParseArgsErr),
					                  ex.Message);
					return 1; // error raised
				}

				// Take the input stream and substitute any scripting variables within it
				SubstituteScriptingVariables(sb);

				// Impersonate the -U username (if needed) and then **execute** the
				// query or script. Note: http connections set the UID and PWD in
				// the connect string
				if (Option_U_specified && !httpConnection)
				{
					WindowsImpersonationContext impersonatedUser;
					if (_DEBUGMODE)
						Console.WriteLine("Windows identity prior to impersonation : " + WindowsIdentity.GetCurrent().Name);
					impersonatedUser = ImpersonateUser(UserName, Domain, Password);
					if (_DEBUGMODE) Console.WriteLine("Windows identity after impersonation : " + WindowsIdentity.GetCurrent().Name);

					ExecuteInput(ConnectString); // execute it

					impersonatedUser.Undo();
					if (_DEBUGMODE)
						Console.WriteLine("Windows identity after impersonation undo : " + WindowsIdentity.GetCurrent().Name);
				}
				else
				{
					ExecuteInput(ConnectString); // execute it
				}

				CloseFiles();
			}
			catch (Exception ex)
			{
				Console.WriteLine(Msg(Resources.locFailedErr), ex.Message);
				return 1; // error raised
			}
			if (Option_o_specified && ExceptionSeen)
				Console.WriteLine(Msg(Resources.locCheckOutputForErr));
			return (ExceptionSeen) ? 1 : 0;
		}

		// Regex match against input string??
		private static bool InputMatch(string regexString, StringBuilder stringBuilder)
		{
			string s = stringBuilder.ToString();
			bool b = Regex.IsMatch(s, regexString);
			return b;
		}

		// Rest of the functions below are supporting routines.

		// --------------------------------------------------------------------
		// Supporting routine -- perform scripting variable substitution
		private static void SubstituteScriptingVariables(StringBuilder stringBuilder)
		{
			Regex expression = new Regex(ScriptingVarRefRegex);

			foreach (Match m in expression.Matches(stringBuilder.ToString()))
			{
				// Pick out just the scripting variable name by remove leading and
				// trailing characters of the scripting variable
				string name = m.ToString().Replace("$(", "").Replace(")", "");

				// Perform the substitution of scripting variable to its value
				if (substituteWith.ContainsKey(name))
				{
					// -v or system-reserved replacement
					stringBuilder.Replace(m.ToString(), substituteWith[name]);
				}
				else if ((Environment.GetEnvironmentVariable(name) != null) &&
				         (!name.StartsWith("ASCMD", StringComparison.CurrentCultureIgnoreCase)))
				{
					// environment variable replacement (cannot be used for system-reserved)
					stringBuilder.Replace(m.ToString(), Environment.GetEnvironmentVariable(name));
				}
				else
				{
					// no match found, so eliminate the scripting variable by
					// replacing it with a blank string
					stringBuilder.Replace(m.ToString(), "");
				}
			}
			if (_DEBUGMODE) Console.WriteLine("After substitution, final input is: " + stringBuilder);
		}

		// --------------------------------------------------------------------
		// Supporting routine -- determine what the connect string should be
		private static string ConnectString
		{
			get
			{
				string cs = "";

				// Build the connect string as required
				// Note: some parameters might have legimite embedded doublequotes "
				//       if so, we marked them with // ** tag
				cs += "Provider=" + Provider;
				if (httpConnection)
				{
					cs += ";Data Source=\"" + Server + "\"";
				}
				else
				{
					cs += ";Data Source=\"" + Server;
					if (Instance.Length > 0) cs += "\\" + Instance;
					cs += "\"";
				}
				if (Database.Length > 0) cs += ";Database=\"" + Database.Replace("\"", "\"\"") + "\""; //**

				// Add http information, if requested
				if (httpConnection && (UserName.Length > 0))
				{
					cs += ";UID=\"" + ((Domain.Length > 0) ? Domain + "\\" + UserName : UserName) + "\"";
					cs += ";PWD=\"" + Password.Replace("\"", "\"\"") + "\""; // **
				}

				// Add timeout information, if requested
				if (Timeout.Length > 0)
					cs += ";Timeout=" + Timeout.ToString(CultureInfo.CurrentCulture);
				if (ConnectTimeout.Length > 0)
					cs += ";Connect Timeout=" + ConnectTimeout.ToString(CultureInfo.CurrentCulture);

				// Add extended connectstring option
				if (ExtendedConnectstring.Length > 0)
					cs += ";" + ExtendedConnectstring.Replace("\"", "\"\""); // **

				// Finally, add our application name :-)
				cs += ";SspropInitAppName=" + AssemblyProduct;

				if (_DEBUGMODE) Console.WriteLine("Connect string: {0}", cs);

				return cs;
			}
		}

		// --------------------------------------------------------------------
		// Supporting routine -- to logon the indicated Windows user (returns
		// the context to undo the impersonation after work is done)
		[SecurityPermission(SecurityAction.Assert, Flags =SecurityPermissionFlag.UnmanagedCode)]
		private static WindowsImpersonationContext ImpersonateUser(string logonUsername, string logonDomain,
		                                                           string logonPassword)
		{
			try
			{
				const int LOGON32_PROVIDER_DEFAULT = 0;
				//This parameter causes LogonUser to create a primary token.
				const int LOGON32_LOGON_INTERACTIVE = 2;

				IntPtr tokenHandle;
				tokenHandle = IntPtr.Zero;

				// Call LogonUser to obtain a handle to an access token.
				if (_DEBUGMODE) Console.WriteLine("Calling LogonUser");
				bool returnValue = NativeMethods.LogonUser(logonUsername, logonDomain, logonPassword,
				                                           LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT,
				                                           ref tokenHandle);

				if (false == returnValue)
				{
					int ret = Marshal.GetLastWin32Error();
					Console.WriteLine(Msg(Resources.locLogonFailedErr), ret);
					throw new Win32Exception(ret);
				}

				if (_DEBUGMODE) Console.WriteLine("Did LogonUser Succeed? " + (returnValue ? "Yes" : "No"));
				if (_DEBUGMODE) Console.WriteLine("Value of Windows NT token: " + tokenHandle);

				// Check the identity.
				if (_DEBUGMODE) Console.WriteLine("Before impersonation: " + WindowsIdentity.GetCurrent().Name);

				// Impersonate using the token handle returned by LogonUser.
				using (WindowsIdentity newId = new WindowsIdentity(tokenHandle))
				{
					return (newId.Impersonate());
				}
			}
			catch (Exception ex)
			{
				Console.WriteLine(Msg(Resources.locImpersonateErr), ex.Message);
				throw;
			}
		}

		// --------------------------------------------------------------------
		// Execute an input statement (MDX query or XMLA script)
		// *** This is the real work of the program -- the rest is supporting functions ***
		private static void ExecuteInput(string ConnectionString)
		{
			Server oServer = new Server();
			//Open up a connection
			try
			{
				oServer.Connect(ConnectionString);
			}
			catch (Exception ex)
			{
				Console.WriteLine(Msg(Resources.locAmoConnErr), ConnectionString, ex.Message);
				throw;
			}

			// Try to execute the query/script
			try
			{
				// Setup to trace events during execution (only if level is not "duration")
				if (Option_T_specified && !(TraceLevel == "duration") && !(TraceLevel == "duration-result"))
				{
					// Initialize trace handler
					oServer.SessionTrace.OnEvent += new TraceEventHandler(SessionTrace_OnEvent);

					//Write some status stuff
					if (_DEBUGMODE)
						Console.WriteLine("Trace connection status to " + Server + " := " +
						                  oServer.Connected.ToString(CultureInfo.CurrentCulture));
					if (_DEBUGMODE)
						Console.WriteLine("Server := " + oServer.Name + " (Session:" +
						                  oServer.SessionID.ToString(CultureInfo.CurrentCulture) + ")");

					// Start the trace
					oServer.SessionTrace.Start();
				}

				// Execute the input batches
				ExecuteBatches(oServer);

				// Stop the trace (if running)            
				if (!oServer.SessionTrace.IsStarted) oServer.SessionTrace.Stop();
			}
			catch (Exception ex)
			{
				Console.WriteLine(Msg(Resources.locAdomdExecutionErr), ex.Message);
				oServer.Dispose();
				throw;
			}

			//Disconnect and end the session on the server
			try
			{
				oServer.Disconnect(true);
			}
			catch (Exception ex)
			{
				Console.WriteLine(Msg(Resources.locAmoDisconnErr), ex.Message);
				throw;
			}

			oServer.Dispose();
		}

		// --------------------------------------------------------------------
		// Execute the batches in the input stream (sb)
		//   A batch is a portion of the input stream separated by "GO" commands, i.e.
		//      <batch>
		//      GO
		//      <batch>
		//      GO
		// 
		private static void ExecuteBatches(Server oServer)
		{
			Regex expression = new Regex(BatchRegex,
			                             RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.Multiline);

			// pickup the input stream, append a GO command and parse it
			MatchCollection matches = expression.Matches(sb + "\ngo\n");

			if (matches.Count > 1) Output("<multiple-batches>");

			foreach (Match m in matches)
			{
				// Pick out just the scripting variable name by remove leading and
				// trailing characters of the scripting variable
				string fullText = m.Value;
				StringBuilder batch = new StringBuilder(fullText.Substring(0, fullText.Length - 3));

				if (_DEBUGMODE) Console.WriteLine("Batch input is: " + batch);
				ExecuteBatch(oServer, batch); // fire the batch
			} // loop for next batch

			if (matches.Count > 1) Output("</multiple-batches>");
		}

		// Now execute the batch itself (which is the lowest level of command to ascmd)
		private static void ExecuteBatch(Server oServer, StringBuilder inp)
		{
			// The result string for the execution
			string Result;

			// Wrap a stopwatch around execution
			Stopwatch sw = new Stopwatch();

			// If this is not an XMLA command (or Discover/Execute raw XMLA request) then
			// we assume that it is an un-encoded Statement without the XML tag.
			// This allows the end-user the convience of just entering a MDX or DMX statement
			// and we will wrap it for them.
			if (!InputMatch(XMLACommandRegex, inp) && !InputMatch(DiscoverRegex, inp) && !InputMatch(ExecuteRegex, inp))
			{
				string stmt = HttpUtility.HtmlEncode(inp.ToString());
				// Recreate in Input string -- wrapping with <Statement> tags
				inp = new StringBuilder(stmt.Length); // allocates space for new batch -- nothing in it
				inp.AppendFormat("<Statement>{0}</Statement>", stmt); // adds the batch input itself
			}

			// There are two different ways to execute the input: a raw XMLA request or an XMLA command.
			// If this is a raw request, then the batch starts with "Discover" or "Execute", else it is an XMLA command
			if (InputMatch(DiscoverRegex, inp) || InputMatch(ExecuteRegex, inp))
			{
				//--------------------------------------------------------------------------------
				// A raw XMLA request:
				// To run a custom full SOAP Envelope request on Analysis Services server, we
				// need to follow 5 steps:
				// 
				// 1. Start the XML/A request and specify its type (Discover or Execute).
				//    For native connections (direct over TCP/IP with DIME protocol), local 
				//    cube connections and stored procedures connections we don't need to
				//    specify the XML/A request type in advance (an Undefined value is
				//    available). But for HTTP and HTTPS connections we need to.
				//
				// 2. Write the xml request (as an xml soap envelope containing the command
				//    for Analysis Services).
				//
				// 3. End the xml request; this will send the previously written request to the 
				//    server for execution.
				//
				// 4. Read/Parse the xml response from the server (with System.Xml.XmlReader).
				//
				// 5. Close the System.Xml.XmlReader from the previous step, to release the 
				//    connection to be used after.
				//--------------------------------------------------------------------------------

				XmlaRequestType r = XmlaRequestType.Undefined;
				if (InputMatch(DiscoverRegex, inp)) r = XmlaRequestType.Discover;
				if (InputMatch(ExecuteRegex, inp)) r = XmlaRequestType.Execute;

				// Wrap the request with a soap envelope and body
				string s = inp.ToString();
				s = String.Format(CultureInfo.CurrentCulture, SoapEnvFormat, s);

				// Start the stopwatch
				sw.Start();

				// Execute the query/script and gather the results
				XmlWriter _xw = oServer.StartXmlaRequest(r);
				_xw.WriteRaw(s);
				using (XmlReader _xr = oServer.EndXmlaRequest())
				{
					// Stop the stopwatch
					sw.Stop();

					// Move in the reader to where the real content begins
					_xr.MoveToContent();

					// Skip past the soap envelope and body (if they exist)
					if (!_xr.EOF && (_xr.Name == "soap:Envelope")) _xr.Read();
					if (!_xr.EOF && (_xr.Name == "soap:Body")) _xr.Read();

					// Gather the results and output them
					Result = GatherResults(_xr);

					// Close the System.Xml.XmlReader, to release the connection for
					// future use.
					_xr.Close();
				}
			}
			else
			{
				//--------------------------------------------------------------------------------
				// An XMLA Command request:

				sw.Start(); // start the stopwatch
				XmlaResultCollection _xrc = oServer.Execute(inp.ToString());
				sw.Stop();

				// Gather the results and output them
				Result = GatherResults(_xrc);
			}
			// Output the result
			Output(Result);

			// If requested, wait for tracing to finish
			if (Option_T_specified)
			{
				if ((TraceLevel == "duration") || (TraceLevel == "duration-result"))
				{
					DurationTrace(sw, Result);
				}
				else
				{
					// is high, medium, low
					WaitForTraceToFinish();
				}
			}
		}

		// --------------------------------------------------------------------
		// Supporting routines -- gather the results

		private static string GatherResults(XmlReader Results)
		{
			string s = Results.ReadOuterXml();

			// -----------------------------------
			// Look for errors.
			// -----------------------------------
			// As an initial pass, we'll just look for some special tags in the
			// stream. This works because the underlying data in the xml stream
			// is html encoded -- thus the only tags that should exist is if there
			// are errors
			if (s.Contains("</soap:Fault>") || // xsd-detected errors
			    s.Contains("</Exception>") // server-detected errors
				)
			{
				ExceptionSeen = true; // tell the main program that we saw errors
			}

			return s;
		}

		[SuppressMessage("Microsoft.Performance", "CA1800:DoNotCastUnnecessarily")]
		private static string GatherResults(XmlaResultCollection Results)
		{
			// XML namespace constants used
			const string xmlns_xmla = "xmlns=\"urn:schemas-microsoft-com:xml-analysis\"";
			const string xmlns_multipleresults =
				"xmlns=\"http://schemas.microsoft.com/analysisservices/2003/xmla-multipleresults\"";
			const string xmlns_mddataset = "xmlns=\"urn:schemas-microsoft-com:xml-analysis:mddataset\"";
			const string xmlns_xsi = "xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"";
			const string xmlns_xsd = "xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\"";
			const string xmlns_empty = "xmlns=\"urn:schemas-microsoft-com:xml-analysis:empty\"";
			const string xmlns_exception = "xmlns=\"urn:schemas-microsoft-com:xml-analysis:exception\"";

			// Format strings constants used
			const string fs_Error = "<Error ErrorCode=\"{0}\" Description=\"{1}\" Source=\"{2}\" HelpFile=\"{3}\" />";
			const string fs_Warning = "<Warning WarningCode=\"{0}\" Description=\"{1}\" Source=\"{2}\" HelpFile=\"{3}\" />";

			// Start to build the output
			StringBuilder _output = new StringBuilder(
				String.Format(CultureInfo.CurrentCulture, "<return {0}>", xmlns_xmla));

			// If there are multiple resultsets, then add the grouping element 
			if (Results.Count > 1)
				_output.Append(String.Format(CultureInfo.CurrentCulture,
				                             "<results {0}>", xmlns_multipleresults));

			// loop through each result in the result set
			foreach (XmlaResult _xr in Results)
			{
				// Is there a value for this result?
				if (_xr.Value.Length > 0)
				{
					// Yes, indicate its type 
					_output.Append(String.Format(CultureInfo.CurrentCulture,
					                             "<root {0} {1} {2}>", xmlns_mddataset, xmlns_xsi, xmlns_xsd));
					_output.Append(_xr.Value); // include the value in the stream
				}
				else
				{
					// Nope, output the empty set for the root
					_output.Append(String.Format(CultureInfo.CurrentCulture,
					                             "<root {0}>", xmlns_empty));
				}

				// Do we have some messages associated with the result? If so, output them
				if (_xr.Messages.Count > 0)
				{
					if (ErrorsExist(_xr))
						_output.Append(String.Format(CultureInfo.CurrentCulture,
						                             "<Exception {0} />", xmlns_exception));

					// Output the messages
					_output.Append(String.Format(CultureInfo.CurrentCulture,
					                             "<Messages {0}>", xmlns_exception));
					foreach (XmlaMessage _xm in _xr.Messages)
					{
						if (_xm is XmlaError) // determine type type
						{
							ExceptionSeen = true; // ERROR bubbles up for the "main" return value

							int ErrorCode = ((XmlaError) _xm).ErrorCode;
							_output.Append(String.Format(CultureInfo.CurrentCulture, fs_Error,
							                             ((uint) ErrorCode).ToString(CultureInfo.CurrentCulture),
							                             HttpUtility.HtmlEncode(_xm.Description), _xm.Source, _xm.HelpFile));
						}
						else
						{
							int WarningCode = ((XmlaWarning) _xm).WarningCode;
							_output.Append(String.Format(CultureInfo.CurrentCulture, fs_Warning,
							                             ((uint) WarningCode).ToString(CultureInfo.CurrentCulture),
							                             HttpUtility.HtmlEncode(_xm.Description), _xm.Source, _xm.HelpFile));
						}
					}
					_output.Append("</Messages>");
				}
				_output.Append("</root>");
			}
			if (Results.Count > 1) _output.Append("</results>");

			_output.Append("</return>");

			// Return the string we've constructed
			return _output.ToString();
		}

		private static bool ErrorsExist(XmlaResult _xr)
		{
			bool ret = false;
			foreach (XmlaMessage _xm in _xr.Messages)
			{
				if (_xm is XmlaError) ret = true;
			}
			return ret;
		}

		private static void WaitForTraceToFinish()
		{
			int delay = Int32.Parse(TraceTimeout, CultureInfo.CurrentCulture);
			TraceTimeoutCount = TraceTimeoutCountReset = delay*PollingInterval; // TraceTimeoutCount is in 1/4 seconds
			// Wait for trace to start to flow
			while (!TraceStarted)
			{
				Thread.Sleep(1);
			} // loop every 20ms waiting for trace to start
			// Wait BeginEndBlockCount becomes 0 or TraceTimeoutCount expires
			while (TraceTimeoutCount > 0)
			{
				// loop forever until TraceFinished
				if (BeginEndBlockCount == 0) return;
				Thread.Sleep(1000/PollingInterval); // wait a polling interval
				TraceTimeoutCount--;
			}
			// TraceTimeoutCount expired -- just exit
		}

		// -------------------------------------------------------------------
		// Supporting routines which handle the trace events
		// Remove control characters, i.e. LF, CR, TAB, etc. and replace them with spaces
		private static string RemoveControlChars(string s)
		{
			StringBuilder stringBuilder = new StringBuilder(s);
			for (int i = 0; i < s.Length; i++)
			{
				if (Char.IsControl(stringBuilder[i]))
					stringBuilder[i] = ' '; // replace all control characters with a space
			}
			return stringBuilder.ToString();
		}

		[SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity")]
		private static void SessionTrace_OnEvent(object sender, TraceEventArgs e)
		{
			string EventClass = e.EventClass.ToString();
			//if (_DEBUGMODE) Console.WriteLine("SessionTrace_OnEvent fired");

			TraceStarted = true; // indicated that we have started to see trace events

			// keep the begin-end block count
			if (EventClass.Contains("Begin")) BeginEndBlockCount++;
			if (EventClass.Contains("End") && (BeginEndBlockCount > 0)) BeginEndBlockCount--;

			// based on the trace level, decide if we should record the event
			// high just falls through -- everything is recorded
			if (TraceLevel == "medium" || TraceLevel == "low")
			{
				if ((EventClass == "ProgressReportCurrent") ||
				    //(EventClass == "ProgressReportBegin") || 
				    //(EventClass == "ProgressReportEnd") || 
				    (EventClass == "Notification")) return; // ignore this event
			}
			if (TraceLevel == "low")
			{
				if (!(EventClass.Contains("End") ||
				      EventClass.Contains("Error"))) return; // if EventClass doesn't contain 'End' or 'Error', ignore this event
			}

			switch (TraceFormat)
			{
				case "text": // a text interpretation of the event
					{
						StringBuilder tm = new StringBuilder(); // trace message
						tm.Append(e.CurrentTime.ToString(CultureInfo.CurrentCulture));
						tm.Append(", " + e.EventClass);
						tm.Append("." + e.EventSubclass);
						foreach (TraceColumn tc in Enum.GetValues(typeof (TraceColumn)))
						{
							if ((tc.ToString() != "CurrentTime") &&
							    (tc.ToString() != "EventClass") &&
							    (tc.ToString() != "EventSubclass"))
							{
								string val = e[tc];
								if (null != val)
								{
									if (tm.Length != 0) tm.Append(", ");
									string v = tc + ": " + RemoveControlChars(val);
									tm.Append(v);
								}
							}
						}
						// Note: For text, output nothing for 'Result' since it is alwasy blank for trace events

						Trace(tm.ToString()); // write trace line
						break;
					}
				case "csv": // a csv interpreation of the event
					{
						StringBuilder tm = new StringBuilder();
						tm.Append(e.CurrentTime.ToString(CultureInfo.CurrentCulture));
						tm.Append(TraceDelim + e.EventClass);
						tm.Append(TraceDelim + e.EventSubclass);
						foreach (TraceColumn tc in Enum.GetValues(typeof (TraceColumn)))
						{
							if ((tc.ToString() != "CurrentTime") &&
							    (tc.ToString() != "EventClass") &&
							    (tc.ToString() != "EventSubclass"))
							{
								string val = e[tc];
								if (tm.Length != 0) tm.Append(TraceDelim);
								if (null != val) tm.Append(RemoveControlChars(val));
							}
						}
						tm.Append(TraceDelim); // For csv, 'Result' is always blank for trace events

						Trace(tm.ToString()); // write trace line
						break;
					}
			}
			TraceTimeoutCount = TraceTimeoutCountReset; // reset the no activity timer
		}

		private static void DurationTrace(Stopwatch sw, string Result)
		{
			string now = DateTime.Now.ToString(CultureInfo.CurrentCulture);
			string Duration = sw.ElapsedMilliseconds.ToString(CultureInfo.CurrentCulture);
			switch (TraceFormat)
			{
				case "text":
					{
						StringBuilder tm = new StringBuilder(); // trace message
						tm.Append(now);
						tm.Append(", Duration: " + Duration);
						tm.Append(", DatabaseName: " + Database);
						tm.Append(", TextData: " + RemoveControlChars(sb.ToString()));
						tm.Append(", ServerName: " + Server);
						if (TraceLevel == "duration-result") tm.Append(", Result: " + Result);
						Trace(tm.ToString()); // write trace line
						break;
					}
				case "csv": // a csv interpreation of the event
					{
						StringBuilder tm = new StringBuilder();
						tm.Append(now);
						tm.Append(TraceDelim); // EventClass
						tm.Append(TraceDelim); // EventSubclass
						foreach (TraceColumn tc in Enum.GetValues(typeof (TraceColumn)))
						{
							if ((tc.ToString() != "CurrentTime") &&
							    (tc.ToString() != "EventClass") &&
							    (tc.ToString() != "EventSubclass"))
							{
								switch (tc.ToString())
								{
									case "Duration":
										{
											tm.Append(TraceDelim);
											tm.Append(Duration);
											break;
										}
									case "DatabaseName":
										{
											tm.Append(TraceDelim);
											tm.Append(Database);
											break;
										}
									case "TextData":
										{
											tm.Append(TraceDelim);
											string val = sb.ToString();
											tm.Append(RemoveControlChars(val));
											break;
										}
									case "ServerName":
										{
											tm.Append(TraceDelim);
											tm.Append(Server);
											break;
										}
									default:
										{
											tm.Append(TraceDelim);
											break;
										}
								}
							}
						}
						tm.Append(TraceDelim);
						if (TraceLevel == "duration-result") tm.Append(Result);

						Trace(tm.ToString()); // write trace line
						break;
					}
			}
		}

		// --------------------------------------------------------------------
		// Supporting routines -- to generate lines in the "Output and Trace files
		private static void Output(string msg)
		{
			if (Option_o_specified)
			{
				// Add support for NUL device
				if (!(OutputFile.Equals("NUL", StringComparison.CurrentCultureIgnoreCase)) &&
				    !(OutputFile.StartsWith("NUL:", StringComparison.CurrentCultureIgnoreCase)))
				{
					// file open delayed until absolutely needed (otherwise we end up overwriting it
					// unnecessarily
					if (!IsOutputFileOpen)
					{
						// open the file
						try
						{
							sw_out = new StreamWriter(OutputFile, false, Encoding.UTF8); // does an overwrite
						}
						catch (Exception ex)
						{
							Console.WriteLine(Msg(Resources.locOutputFileOpenErr), ex.Message);
							throw;
						}
						IsOutputFileOpen = true;
					}
					sw_out.WriteLine(msg);
				}
			}
			else
			{
				Console.WriteLine(msg);
			}
		}

		private static void Trace(string msg)
		{
			if (!IsTraceFileOpen)
			{
				bool appendFlag;

				// file open delayed until absolutely needed
				try
				{
					// if doing duration trace and file already exists, then don't overwrite
					// but instead do an append
					appendFlag = (((TraceLevel == "duration") || (TraceLevel == "duration-result")) &&
					              File.Exists(TraceFile))
					             	? true
					             	: false;
					sw_trace = new StreamWriter(TraceFile, appendFlag, Encoding.UTF8);
				}
				catch (Exception ex)
				{
					Console.WriteLine(Msg(Resources.locTraceFileOpenErr), ex.Message);
					throw;
				}
				IsTraceFileOpen = true;

				// Ok Trace file is now ready to go

				// If csv trace file format and we are not appending to the end, then
				// output the header line in the trace
				if ((TraceFormat == "csv") && !appendFlag)
				{
					StringBuilder tm = new StringBuilder();
					tm.Append("CurrentTime" + TraceDelim +
					          "EventClass" + TraceDelim + "EventSubclass");
					foreach (TraceColumn tc in Enum.GetValues(typeof (TraceColumn)))
					{
						if ((tc.ToString() != "CurrentTime") &&
						    (tc.ToString() != "EventClass") &&
						    (tc.ToString() != "EventSubclass"))
						{
							tm.Append(TraceDelim);
							tm.Append(tc);
						}
					}
					tm.Append(TraceDelim + "Result");

					Trace(tm.ToString());
				}
			}

			// write out the trace line
			try
			{
				sw_trace.WriteLine(msg);
			}
			catch (Exception ex)
			{
				Console.WriteLine(Msg(Resources.locTraceWriteErr), ex.Message);
				throw;
			}
		}

		// --------------------------------------------------------------------
		// Supporting routines -- close all files
		private static void CloseFiles()
		{
			// OK, we are all done. Perform some file cleanup and exit.
			if (IsOutputFileOpen) CloseFile(sw_out, Msg(Resources.locOutputFileCloseErr));
			if (IsTraceFileOpen) CloseFile(sw_trace, Msg(Resources.locTraceFileCloseErr));
		}

		private static void CloseFile(StreamWriter sw, string errorMsg)
		{
			try
			{
				sw.Close();
			}
			catch (Exception ex)
			{
				Console.WriteLine(errorMsg, ex.Message);
				throw;
			}
		}

		// --------------------------------------------------------------------
		// Supporting routine -- to parse the command line
		[SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity")]
		private static bool ParseArgs(string[] args)
		{
			if (args.Length == 0) // nothing on the command-line
			{
				// as a minimum -i or -Q must be specified
				Console.WriteLine(Msg(Resources.locInpEitherErr));
				ShowHelp();
				return false;
			}

			GetScriptingVariables(); // Get scripting variables from the OS

			// Now loop through the command-line args
			for (int i = 0; i < args.Length; i++)
			{
				string arg = args[i].ToString(CultureInfo.CurrentCulture);
				if (_DEBUGMODE) Console.WriteLine(arg);

				switch (arg)
				{
					case "/?": // Unix way of requesting help
					case "-?": // and this is the Windows way :-)
						{
							ShowHelp();
							return false;
						}
					case "-U": // User Id
						{
							if (Option_U_specified) return OptionSeen("-U");
							Option_U_specified = true;

							if (i == (args.Length - 1)) return MissingArg("-U"); // option must have an argument
							UserName = args[++i].ToString(CultureInfo.CurrentCulture);
							if (_DEBUGMODE) Console.WriteLine("*" + UserName);
							break;
						}
					case "-P": // Password
						{
							if (Option_P_specified) return OptionSeen("-P");
							Option_P_specified = true;

							if (i == (args.Length - 1)) return MissingArg("-P"); // option must have an argument
							Password = args[++i].ToString(CultureInfo.CurrentCulture);
							if (_DEBUGMODE) Console.WriteLine("*" + Password);
							break;
						}
					case "-S": // Server
						{
							if (Option_S_specified) return OptionSeen("-S");
							Option_S_specified = true;

							if (i == (args.Length - 1)) return MissingArg("-S"); // option must have an argument
							Server = args[++i].ToString(CultureInfo.CurrentCulture);
							if (_DEBUGMODE) Console.WriteLine("*" + Server);
							break;
						}
					case "-d": // Database
						{
							if (Option_d_specified) return OptionSeen("-d");
							Option_d_specified = true;

							if (i == (args.Length - 1)) return MissingArg("-d"); // option must have an argument
							Database = args[++i].ToString(CultureInfo.CurrentCulture);
							if (_DEBUGMODE) Console.WriteLine("*" + Database);
							break;
						}
					case "-t": // Timeout (for query)
						{
							if (Option_t_specified) return OptionSeen("-t");
							Option_t_specified = true;

							if (i == (args.Length - 1)) return MissingArg("-t"); // option must have an argument
							Timeout = args[++i].ToString(CultureInfo.CurrentCulture);
							if (_DEBUGMODE) Console.WriteLine("*" + Timeout.ToString(CultureInfo.CurrentCulture));
							break;
						}
					case "-tc": // Timeout (for connections)
						{
							if (Option_tc_specified) return OptionSeen("-tc");
							Option_tc_specified = true;

							if (i == (args.Length - 1)) return MissingArg("-tc"); // option must have an argument
							ConnectTimeout = args[++i].ToString(CultureInfo.CurrentCulture);
							if (_DEBUGMODE) Console.WriteLine("*" + ConnectTimeout.ToString(CultureInfo.CurrentCulture));
							break;
						}
					case "-i": // InputFile
						{
							if (Option_i_specified) return OptionSeen("-i");
							Option_i_specified = true;

							if (i == (args.Length - 1)) return MissingArg("-i"); // option must have an argument
							InputFile = args[++i].ToString(CultureInfo.CurrentCulture);
							if (_DEBUGMODE) Console.WriteLine("*" + InputFile);
							// Validate the file exists
							if (File.Exists(InputFile))
							{
								// Read in the query or script from the input file
								try
								{
									// Force the reader to be UTF8 and to look for byte-order marker in file
									StreamReader sr = new StreamReader(InputFile, Encoding.UTF8, true);
									sb.Append(sr.ReadToEnd().Trim());
								}
								catch (Exception ex)
								{
									Console.WriteLine(Msg(Resources.locInpFileGeneralErr), InputFile, ex.Message);
									throw;
								}
								// If input file is empty, return an error.
								if (sb.Length == 0)
								{
									Console.WriteLine(Msg(Resources.locInpFileEmptyErr), InputFile);
									return false;
								}
							}
							else
							{
								// Input file does not exist
								Console.WriteLine(Msg(Resources.locInpFileNotExistErr), InputFile);
								return false;
							}
							break;
						}
					case "-o": // OutputFile
						{
							if (Option_o_specified) return OptionSeen("-o");
							Option_o_specified = true;

							if (i == (args.Length - 1)) return MissingArg("-o"); // option must have an argument
							OutputFile = args[++i].ToString(CultureInfo.CurrentCulture);
							if (_DEBUGMODE) Console.WriteLine("*" + OutputFile);
							// Make sure the output file name format is OK
							if (!CheckValidChars(Resources.locNameOutputFile,
							                     1, -1, "", OutputFile)) return false;
							break;
						}
					case "-Q": // command line query
						{
							if (Option_Q_specified) return OptionSeen("-Q");
							Option_Q_specified = true;

							if (i == (args.Length - 1)) return MissingArg("-Q"); // option must have an argument
							Query = args[++i].ToString(CultureInfo.CurrentCulture);
							if (_DEBUGMODE) Console.WriteLine("*" + "\"" + Query + "\"");

							// If input file is empty, return an error.
							if (Query.Length == 0)
							{
								Console.WriteLine(Msg(Resources.locOptionQEmptyErr));
								return false;
							}
							else
							{
								sb.Append(Query.Trim());
							}
							break;
						}
					case "-T": // TraceFile
						{
							if (Option_T_specified) return OptionSeen("-T");
							Option_T_specified = true;

							if (i == (args.Length - 1)) return MissingArg("-T"); // option must have an argument
							TraceFile = args[++i].ToString(CultureInfo.CurrentCulture);
							if (_DEBUGMODE) Console.WriteLine("*" + TraceFile);
							// Make sure the trace file name format is OK
							if (!CheckValidChars(Resources.locNameTraceFile,
							                     1, -1, "", TraceFile)) return false;
							break;
						}
					case "-Tf": // Trace file format (only valid if -T is also specified)
						{
							if (Option_Tf_specified) return OptionSeen("-Tf");
							Option_Tf_specified = true;

							if (i == (args.Length - 1)) return MissingArg("-Tf"); // option must have an argument
							TraceFormat = args[++i].ToString(CultureInfo.CurrentCulture);
							if (_DEBUGMODE) Console.WriteLine("*" + TraceFormat.ToString(CultureInfo.CurrentCulture));
							break;
						}
					case "-Tl": // Trace file level (only valid if -T is also specified)
						{
							if (Option_Tl_specified) return OptionSeen("-Tl");
							Option_Tl_specified = true;

							if (i == (args.Length - 1)) return MissingArg("-Tl"); // option must have an argument
							TraceLevel = args[++i].ToString(CultureInfo.CurrentCulture);
							if (_DEBUGMODE) Console.WriteLine("*" + TraceLevel.ToString(CultureInfo.CurrentCulture));
							break;
						}
					case "-Td": // Trace file delimiter (default is "|", vertical bar or pipe)
						{
							if (Option_Td_specified) return OptionSeen("-Td");
							Option_Td_specified = true;

							if (i == (args.Length - 1)) return MissingArg("-Td"); // option must have an argument
							TraceDelim = args[++i].ToString(CultureInfo.CurrentCulture);
							if (_DEBUGMODE) Console.WriteLine("*" + ExtendedConnectstring.ToString(CultureInfo.CurrentCulture));
							break;
						}
					case "-Tt": // Timeout (for trace) -- indicates the amount of inactivity that signals trace is complete
						{
							if (Option_Tt_specifed) return OptionSeen("-Tt");
							Option_Tt_specifed = true;

							if (i == (args.Length - 1)) return MissingArg("-Tt"); // option must have an argument
							TraceTimeout = args[++i].ToString(CultureInfo.CurrentCulture);
							if (_DEBUGMODE) Console.WriteLine("*" + TraceTimeout.ToString(CultureInfo.CurrentCulture));
							break;
						}
					case "-xc": // Extended connectstring
						{
							if (Option_xc_specified) return OptionSeen("-xc");
							Option_xc_specified = true;

							if (i == (args.Length - 1)) return MissingArg("-xc"); // option must have an argument
							ExtendedConnectstring = args[++i].ToString(CultureInfo.CurrentCulture);
							if (_DEBUGMODE) Console.WriteLine("*" + ExtendedConnectstring.ToString(CultureInfo.CurrentCulture));
							// No checks made -- whatever you enter will be placed on the connect sttring
							break;
						}
					case "-v": // Set scripting variables
						{
							if (Option_v_specified) return OptionSeen("-v");
							Option_v_specified = true;

							// -v options are a bit different.
							// They are in the form '-v name=value name=value name=value'

							// Loop through arguments until we no longer get a name=value pair 
							while (i < (args.Length - 1)) // don't loop off the end
							{
								string Entry = args[++i].ToString(CultureInfo.CurrentCulture);

								// look to see if the arg is name=value where name is a
								// valid scripting variable
								Regex expression = new Regex(ScriptingVarNameRegex);
								if (!expression.IsMatch(Entry))
								{
									i -= 1; // Nope, backup and retry the argument as an option 
									break;
								}
								else
								{
									// Ok if we got here then this is a -v entry
									// Parse it into its name=value pair
									string[] ParsedEntry = Entry.Split(new Char[] {'='}, 2);
									string name = ParsedEntry[0];
									string value = ParsedEntry[1];
									if (_DEBUGMODE) Console.WriteLine("-v Name:*" + name);
									if (_DEBUGMODE) Console.WriteLine("-v Value:*" + value);
									// Validate the name -- it must not start with 'ASCMD'
									// that is reservered for command-line options
									if (name.StartsWith("ASCMD",
									                    StringComparison.CurrentCultureIgnoreCase))
									{
										Console.WriteLine(Msg(Resources.locScriptingVarInvalidNameErr), Entry);
										return false;
									}
									else
									{
										// add it to substitutionWith table
										if (!substituteWith.ContainsKey(name))
										{
											// Entry is not already known, add it
											substituteWith.Add(name, value);
										}
										else
										{
											// Entry already there, override it
											// Note: this means that if a name is specified
											// more than once, then the last name=value pair
											// on the command line wins
											substituteWith[name] = value;
										}
									}
								}
							}
							break;
						}
					case "-DEBUG": // set debug mode
						{
							// no argument
							_DEBUGMODE = true;
							break;
						}
					default: // error
						{
							Console.WriteLine(Msg(Resources.locUnknownOptionErr), arg);
							return false;
						}
				}
			}

			// Verify the current settings -- returns false if an error found
			if (!ValidateSettings()) return false;

			// Verify input stream
			// 1) command-line must have either -i or -Q, but not both
			// 2) there must be something to actually execute (some script or
			//    query)
			if (Option_i_specified && Option_Q_specified)
			{
				// both
				Console.WriteLine(Msg(Resources.locInpNotBothErr));
				return false;
			}
			else if (!(Option_i_specified || Option_Q_specified))
			{
				// either -i or -Q must be present
				Console.WriteLine(Msg(Resources.locInpEitherErr));
				return false;
			}
			else if (sb.Length == 0)
			{
				// If we got here then we don't have anything to execute,
				// Note: This is not really needed, because empty checking is 
				// also in both the -Q and -i option parsing
				Console.WriteLine(Msg(Resources.locNoInpErr)); //
				return false;
			}

			// You cannot run trace over an http or https connection
			if (Option_T_specified && httpConnection)
			{
				Console.WriteLine(Msg(Resources.locTraceNoHttpErr));
				return false;
			}

			// Set scripting variables
			SetScriptingVariables();

			return true; // All of the options are parsed and files are open ready to go
		}

		// --------------------------------------------------------------------
		// Helper routines for ParseArgs

		private static bool ValidateSettings()
		{
			if (!ValidateOption_S) return false; // This must be done first so the httpConnection can be set first
			if (!ValidateOption_U) return false;
			if (!ValidateOption_P) return false;
			if (!ValidateOption_d) return false;
			if (!ValidateTimeout("-t", Timeout)) return false;
			if (!ValidateTimeout("-tc", ConnectTimeout)) return false;
			if (!ValidateOption_Tf) return false;
			if (!ValidateOption_Tl) return false;
			if (!ValidateOption_Td) return false;
			if (!ValidateTimeout("-Tt", TraceTimeout)) return false;
			return true;
		}

		// -S validate
		// NOTE: This must be done first so the httpConnection can be set first
		private static bool ValidateOption_S
		{
			get
			{
				// Is this a http or https connection??
				string s = Server.ToLower(CultureInfo.CurrentCulture);
				if (s.StartsWith("http://") || s.StartsWith("https://"))
				{
					httpConnection = true;
					if (_DEBUGMODE) Console.WriteLine("Http Server:*" + Server);
					if (!CheckValidChars(Resources.locNameServer,
					                     8, -1, "", Server)) return false;
				}
				else
				{
					// normal client-server connections -- either just the server or multi-instance
					httpConnection = false;
					if (Server.Contains("\\")) // backslash anywhere on line?
					{
						// multi-instance server name (i.e. <server>\<instance>
						string[] ParsedServer = Server.Split(new Char[] {'\\'}, 2);
						string partServer = ParsedServer[0];
						string partInstance = ParsedServer[1];
						if (_DEBUGMODE) Console.WriteLine("Server:*" + partServer);
						if (_DEBUGMODE) Console.WriteLine("Instance:*" + partInstance);
						// Check for valid server and instance name
						// Note: the \ character not in Invalid char list since we support named instances
						if (!CheckValidChars(Resources.locNameServer,
						                     1, -1, "", partServer)) return false;
						if (!CheckValidChars(Resources.locNameInstance,
						                     1, -1, "", partInstance)) return false;
						Server = partServer;
						Instance = partInstance;
					}
					else
					{
						// just a plain old server name
						if (_DEBUGMODE) Console.WriteLine("Server:*" + Server);
						if (!CheckValidChars(Resources.locNameServer,
						                     1, -1, "", Server)) return false;
					}
				}
				return true;
			}
		}

		// --------------------
		// -U validate
		private static bool ValidateOption_U
		{
			get
			{
				if (UserName.Length > 0)
				{
					if (UserName.Contains("\\"))
					{
						string[] ParsedUserName = UserName.Split(new Char[] {'\\'}, 2);
						Domain = ParsedUserName[0];
						UserName = ParsedUserName[1];
					}
					else if (!httpConnection)
					{
						// For a TCP/IP connection, the username must be in the format <domain>\<username>
						Console.WriteLine(Msg(Resources.locOptionUFormatErr));
						return false;
					}
					if (!CheckValidChars(Resources.locNameUsername,
					                     1, -1, "", UserName)) return false;
					if (!CheckValidChars(Resources.locNameDomain,
					                     1, -1, "", Domain)) return false;
				}
				return true;
			}
		}

		// --------------------
		// -P validate
		private static bool ValidateOption_P
		{
			get
			{
				if (Password.Length > 0)
				{
					if (!CheckValidChars(Resources.locNamePassword,
					                     0, -1, "", Password)) return false;
				}
				return true;
			}
		}

		// --------------------
		// -d validate
		private static bool ValidateOption_d
		{
			get
			{
				if (Database.Length > 0)
				{
					if (!CheckValidChars(Resources.locNameDatabase,
					                     1, 128, "", Database)) return false;
				}
				return true;
			}
		}


		// --------------------
		// -Tf validate
		private static bool ValidateOption_Tf
		{
			get
			{
				// only 2 supported options -- validate their names here
				TraceFormat = TraceFormat.ToLower(CultureInfo.CurrentCulture);
				if ((TraceFormat != "text") && (TraceFormat != "csv"))
				{
					Console.WriteLine(Msg(Resources.locTraceInvalidFormatErr), TraceFormat);
					return false;
				}
				return true;
			}
		}

		// --------------------
		// -Tl validate
		private static bool ValidateOption_Tl
		{
			get
			{
				// only 4 supported options -- validate their names here
				TraceLevel = TraceLevel.ToLower(CultureInfo.CurrentCulture);
				if ((TraceLevel != "high") && (TraceLevel != "medium") &&
				    (TraceLevel != "low") && (TraceLevel != "duration") &&
				    (TraceLevel != "duration-result"))
				{
					Console.WriteLine(Msg(Resources.locTraceInvalidLevelErr), TraceLevel);
					return false;
				}
				return true;
			}
		}

		// --------------------
		// -Td validate
		private static bool ValidateOption_Td
		{
			get
			{
				if (TraceDelim.Length > 1)
				{
					Console.WriteLine(Msg(Resources.locTraceInvalidDelimErr), TraceDelim);
					return false;
				}
				return true;
			}
		}

		// --------------------
		// validate a timeout value
		private static bool ValidateTimeout(string Option, string timeOut)
		{
			if (timeOut.Length > 0)
			{
				int t;
				if ((t = GetTimeout(Option, timeOut)) == -1) return false;
				if (!CheckTimeout(Option, t, maxTimeout)) return false;
			}
			return true;
		}

		// ------------------------------------------------------------
		// Get the available scripting variables from the OS
		private static void GetScriptingVariables()
		{
			if (Environment.GetEnvironmentVariable("ASCMDUSER") != null)
				UserName = Environment.GetEnvironmentVariable("ASCMDUSER");
			if (Environment.GetEnvironmentVariable("ASCMDPASSWORD") != null)
				Password = Environment.GetEnvironmentVariable("ASCMDPASSWORD");
			if (Environment.GetEnvironmentVariable("ASCMDSERVER") != null)
				Server = Environment.GetEnvironmentVariable("ASCMDSERVER");
			if (Environment.GetEnvironmentVariable("ASCMDDBNAME") != null)
				Database = Environment.GetEnvironmentVariable("ASCMDDBNAME");
			if (Environment.GetEnvironmentVariable("ASCMDQUERYTIMEOUT") != null)
				Timeout = Environment.GetEnvironmentVariable("ASCMDQUERYTIMEOUT");
			if (Environment.GetEnvironmentVariable("ASCMDCONNECTTIMEOUT") != null)
				ConnectTimeout = Environment.GetEnvironmentVariable("ASCMDCONNECTTIMEOUT");
			if (Environment.GetEnvironmentVariable("ASCMDTRACEFORMAT") != null)
				TraceFormat = Environment.GetEnvironmentVariable("ASCMDTRACEFORMAT");
			if (Environment.GetEnvironmentVariable("ASCMDTRACEDELIM") != null)
				TraceDelim = Environment.GetEnvironmentVariable("ASCMDTRACEDELIM");
			if (Environment.GetEnvironmentVariable("ASCMDTRACETIMEOUT") != null)
				TraceTimeout = Environment.GetEnvironmentVariable("ASCMDTRACETIMEOUT");
			if (Environment.GetEnvironmentVariable("ASCMDTRACELEVEL") != null)
				TraceLevel = Environment.GetEnvironmentVariable("ASCMDTRACELEVEL");
			if (Environment.GetEnvironmentVariable("ASCMDEXTENDEDCONNECTSTRING") != null)
				ExtendedConnectstring = Environment.GetEnvironmentVariable("ASCMDEXTENDEDCONNECTSTRING");
		}

		private static void SetScriptingVariables()
		{
			// The -v entries are added to the substituteWith table as they are parsed
			// Here we add command-line entries to the substitution table
			//  * = read-only, cannot be set by outside environments
			// ** = set by ValidateSettings
			substituteWith.Add("ASCMDUSER", UserName);
			substituteWith.Add("ASCMDDOMAIN", Domain); // **
			substituteWith.Add("ASCMDPASSWORD", Password);
			substituteWith.Add("ASCMDSERVER", Server);
			substituteWith.Add("ASCMDINSTANCE", Instance); // **
			substituteWith.Add("ASCMDHTTPCONNECTION", httpConnection.ToString()); // **
			substituteWith.Add("ASCMDDBNAME", Database);
			substituteWith.Add("ASCMDINPUTFILE", InputFile); // *
			substituteWith.Add("ASCMDOUTPUTFILE", OutputFile); // *
			substituteWith.Add("ASCMDQUERYTIMEOUT", Timeout);
			substituteWith.Add("ASCMDCONNECTTIMEOUT", ConnectTimeout);
			substituteWith.Add("ASCMDTRACEFILE", TraceFile); // *
			substituteWith.Add("ASCMDTRACEFORMAT", TraceFormat);
			substituteWith.Add("ASCMDTRACEDELIM", TraceDelim);
			substituteWith.Add("ASCMDTRACETIMEOUT", TraceTimeout);
			substituteWith.Add("ASCMDTRACELEVEL", TraceLevel);
			substituteWith.Add("ASCMDEXTENDEDCONNECTSTRING", ExtendedConnectstring);
		}

		private static bool CheckValidChars(string TypeOfCheck, int MinLen, int MaxLen, string InvalidChars, string TestString)
		{
			if (MinLen >= 0) // negative MinLen means don't check it
				if (TestString.Length < MinLen)
				{
					Console.WriteLine(Msg(Resources.locMinErr), TypeOfCheck, TestString,
					                  TestString.Length.ToString(CultureInfo.CurrentCulture),
					                  MinLen.ToString(CultureInfo.CurrentCulture));
					return false;
				}
			if (MaxLen >= 0) // negative MaxLen means don't check it
				if (TestString.Length > MaxLen)
				{
					Console.WriteLine(Msg(Resources.locMaxErr), TypeOfCheck, TestString,
					                  TestString.Length.ToString(CultureInfo.CurrentCulture),
					                  MaxLen.ToString(CultureInfo.CurrentCulture));
					return false;
				}
			if (TestString.Length > 0) // if we don't have any invalid characters provided, then don't check
			{
				int pos = TestString.IndexOfAny(InvalidChars.ToCharArray()) + 1;
				if (pos > 0)
				{
					Console.WriteLine(Msg(Resources.locInvalidCharErr), TypeOfCheck, TestString,
					                  pos.ToString(CultureInfo.CurrentCulture));
					return false;
				}
			}
			return true;
		}

		private static int GetTimeout(string TypeOfCheck, string timeOut)
		{
			int ReturnTimeout;
			if (Int32.TryParse(timeOut.ToString((CultureInfo.CurrentCulture)),
			                   out ReturnTimeout))
			{
				return ReturnTimeout;
			}
			else
			{
				Console.WriteLine(Msg(Resources.locIntegerErr), TypeOfCheck, timeOut);
				return -1;
			}
		}

		private static bool CheckTimeout(string TypeOfCheck, int timeOut, int MaxTimeout)
		{
			if (MaxTimeout > 0) // -1 maxTimeout won't run check
			{
				if ((timeOut <= 0) || (timeOut > MaxTimeout))
				{
					Console.WriteLine(Msg(Resources.locRangeErr), TypeOfCheck, timeOut, MaxTimeout);
					return false;
				}
			}
			return true;
		}

		private static bool OptionSeen(string opt)
		{
			Console.WriteLine(Msg(Resources.locRepeatedOption), opt);
			return false;
		}

		private static bool MissingArg(string opt)
		{
			Console.WriteLine(Msg(Resources.locParseMissingArgErr), opt);
			return false;
		}

		// --------------------------------------------------------------------
		// Supporting routine -- to show the help message
		private static void ShowHelp()
		{
			string msg = "\n" +
			             "usage: ascmd.exe" +
			             "\n  [-S server[:port][\\instance]] " +
			             "\n       || http[s]://server:port/<vdir>/msmdpump.dll " +
			             "\n  [-U domain\\username]   [-P password] " +
			             "\n  [-d database]          [-xc extended-connect-string] " +
			             "\n  [-i input-file]        [-o output-file] " +
			             "\n  [-t query-timeout]     [-tc connect-timeout] " +
			             "\n  [-T trace-file]        [-Tt trace-timeout] " +
			             "\n  [-Tf text|csv]         [-Td delim-char] " +
			             "\n  [-Tl high|medium|low|duration|duration-result] " +
			             "\n  [-v var=value...] " +
			             "\n  [-Q \"cmdline XMLA script, MDX query or DMX statement\" " +
			             "\n  [-? show syntax summary] " +
			             "\n\n Note: either -i or -Q must be specified, but not both.";
			// OK write it out
			Console.WriteLine(msg);
		}
	}
}